/**
 * GitCode API 服务单元测试
 * 
 * 测试请求构建和错误处理
 * Requirements: 7.1
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { GitCodeApiService, GitCodeApiError } from './GitCodeApiService';
import type { ApiChatMessage } from '../types';

// 声明 globalThis.fetch 类型
declare const globalThis: {
  fetch: typeof fetch;
};

describe('GitCodeApiService', () => {
  let service: GitCodeApiService;

  beforeEach(() => {
    service = new GitCodeApiService({ apiKey: 'test-api-key' });
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('配置管理', () => {
    it('应使用默认配置初始化', () => {
      const defaultService = new GitCodeApiService();
      const config = defaultService.getConfig();
      
      expect(config.apiUrl).toBe('https://api.gitcode.com/api/v1/chat/completions');
      expect(config.model).toBe('Qwen3-VL-30B');
      expect(config.maxTokens).toBe(2048);
      expect(config.temperature).toBe(0.7);
    });

    it('应允许自定义配置覆盖默认值', () => {
      const customService = new GitCodeApiService({
        apiKey: 'custom-key',
        model: 'custom-model',
        temperature: 0.5,
      });
      const config = customService.getConfig();
      
      expect(config.apiKey).toBe('custom-key');
      expect(config.model).toBe('custom-model');
      expect(config.temperature).toBe(0.5);
      // 未覆盖的值应保持默认
      expect(config.maxTokens).toBe(2048);
    });

    it('应能更新配置', () => {
      service.updateConfig({ temperature: 0.9 });
      const config = service.getConfig();
      
      expect(config.temperature).toBe(0.9);
      expect(config.apiKey).toBe('test-api-key');
    });
  });

  describe('请求构建', () => {
    it('应正确构建非流式请求体', () => {
      const messages: ApiChatMessage[] = [
        { role: 'user', content: '你好' },
      ];
      
      const body = service.buildRequestBody(messages, false);
      
      expect(body).toEqual({
        model: 'Qwen3-VL-30B',
        messages: [{ role: 'user', content: '你好' }],
        max_tokens: 2048,
        temperature: 0.7,
        stream: false,
      });
    });

    it('应正确构建流式请求体', () => {
      const messages: ApiChatMessage[] = [
        { role: 'system', content: '你是一个助手' },
        { role: 'user', content: '帮我写代码' },
      ];
      
      const body = service.buildRequestBody(messages, true);
      
      expect(body).toEqual({
        model: 'Qwen3-VL-30B',
        messages: [
          { role: 'system', content: '你是一个助手' },
          { role: 'user', content: '帮我写代码' },
        ],
        max_tokens: 2048,
        temperature: 0.7,
        stream: true,
      });
    });

    it('应正确构建请求头', () => {
      const headers = service.buildHeaders();
      
      expect(headers).toEqual({
        'Content-Type': 'application/json',
        'Authorization': 'Bearer test-api-key',
      });
    });

    it('应支持多模态消息内容', () => {
      const messages: ApiChatMessage[] = [
        {
          role: 'user',
          content: [
            { type: 'text', text: '这是什么图片？' },
            { type: 'image_url', image_url: { url: '' } },
          ],
        },
      ];
      
      const body = service.buildRequestBody(messages, false);
      
      expect(body).toHaveProperty('messages');
      const builtMessages = (body as { messages: ApiChatMessage[] }).messages;
      expect(builtMessages[0].content).toEqual([
        { type: 'text', text: '这是什么图片？' },
        { type: 'image_url', image_url: { url: '' } },
      ]);
    });
  });

  describe('错误处理', () => {
    it('应在 API 密钥未配置时抛出错误', async () => {
      const noKeyService = new GitCodeApiService();
      
      await expect(noKeyService.chat([{ role: 'user', content: 'test' }]))
        .rejects
        .toThrow(GitCodeApiError);
      
      await expect(noKeyService.chat([{ role: 'user', content: 'test' }]))
        .rejects
        .toMatchObject({
          code: 'API_KEY_MISSING',
          message: 'API 密钥未配置',
        });
    });

    it('应在 API 请求失败时抛出带状态码的错误', async () => {
      vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
        ok: false,
        status: 401,
        text: async () => 'Unauthorized',
      } as Response);

      await expect(service.chat([{ role: 'user', content: 'test' }]))
        .rejects
        .toMatchObject({
          code: 'API_REQUEST_FAILED',
          statusCode: 401,
        });
    });

    it('应在 API 返回空响应时抛出错误', async () => {
      vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
        ok: true,
        json: async () => ({ choices: [] }),
      } as Response);

      await expect(service.chat([{ role: 'user', content: 'test' }]))
        .rejects
        .toMatchObject({
          code: 'EMPTY_RESPONSE',
        });
    });

    it('应在 API 返回内容为空时抛出错误', async () => {
      vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          choices: [{ message: { content: null } }],
        }),
      } as Response);

      await expect(service.chat([{ role: 'user', content: 'test' }]))
        .rejects
        .toMatchObject({
          code: 'EMPTY_CONTENT',
        });
    });

    it('应在网络错误时抛出友好错误', async () => {
      vi.spyOn(globalThis, 'fetch').mockRejectedValueOnce(
        new TypeError('Failed to fetch')
      );

      await expect(service.chat([{ role: 'user', content: 'test' }]))
        .rejects
        .toMatchObject({
          code: 'NETWORK_ERROR',
          message: '网络连接失败',
        });
    });
  });

  describe('chat 方法', () => {
    it('应成功返回 API 响应内容', async () => {
      vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          id: 'test-id',
          choices: [
            {
              message: {
                role: 'assistant',
                content: '你好！有什么可以帮助你的？',
              },
            },
          ],
        }),
      } as Response);

      const result = await service.chat([{ role: 'user', content: '你好' }]);
      
      expect(result).toBe('你好！有什么可以帮助你的？');
    });
  });

  describe('chatStream 方法', () => {
    it('应在 API 密钥未配置时抛出错误', async () => {
      const noKeyService = new GitCodeApiService();
      
      await expect(
        noKeyService.chatStream([{ role: 'user', content: 'test' }], () => {})
      ).rejects.toMatchObject({
        code: 'API_KEY_MISSING',
      });
    });

    it('应在响应体为空时抛出错误', async () => {
      vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
        ok: true,
        body: null,
      } as Response);

      await expect(
        service.chatStream([{ role: 'user', content: 'test' }], () => {})
      ).rejects.toMatchObject({
        code: 'EMPTY_RESPONSE_BODY',
      });
    });

    it('应正确处理流式响应', async () => {
      const chunks = [
        'data: {"choices":[{"delta":{"content":"你"}}]}\n',
        'data: {"choices":[{"delta":{"content":"好"}}]}\n',
        'data: {"choices":[{"delta":{"content":"！"}}]}\n',
        'data: [DONE]\n',
      ];

      const encoder = new TextEncoder();
      let chunkIndex = 0;

      const mockReader = {
        read: vi.fn().mockImplementation(async () => {
          if (chunkIndex < chunks.length) {
            const chunk = chunks[chunkIndex++];
            return { done: false, value: encoder.encode(chunk) };
          }
          return { done: true, value: undefined };
        }),
      };

      vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
        ok: true,
        body: {
          getReader: () => mockReader,
        },
      } as unknown as Response);

      const receivedChunks: string[] = [];
      const result = await service.chatStream(
        [{ role: 'user', content: 'test' }],
        (chunk) => receivedChunks.push(chunk)
      );

      expect(result).toBe('你好！');
      expect(receivedChunks).toEqual(['你', '好', '！']);
    });
  });

  describe('analyzeImage 方法', () => {
    it('应正确构建多模态请求', async () => {
      const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          choices: [
            {
              message: {
                content: '这是一张聊天截图',
              },
            },
          ],
        }),
      } as Response);

      await service.analyzeImage('base64data', '请分析这张图片');

      expect(fetchSpy).toHaveBeenCalledTimes(1);
      const callArgs = fetchSpy.mock.calls[0];
      const requestBody = JSON.parse(callArgs[1]?.body as string);
      
      expect(requestBody.messages[0].content).toEqual([
        { type: 'text', text: '请分析这张图片' },
        { type: 'image_url', image_url: { url: '' } },
      ]);
    });

    it('应支持自定义 MIME 类型', async () => {
      const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          choices: [{ message: { content: 'result' } }],
        }),
      } as Response);

      await service.analyzeImage('base64data', 'prompt', 'image/jpeg');

      const requestBody = JSON.parse(fetchSpy.mock.calls[0][1]?.body as string);
      expect(requestBody.messages[0].content[1].image_url.url)
        .toBe('');
    });
  });

  describe('静态工具方法', () => {
    describe('validateImageFile', () => {
      it('应接受有效的图片格式', () => {
        const validTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp'];
        
        for (const type of validTypes) {
          const file = new File([''], 'test', { type });
          const result = GitCodeApiService.validateImageFile(file);
          expect(result.valid).toBe(true);
        }
      });

      it('应拒绝无效的图片格式', () => {
        const file = new File([''], 'test.pdf', { type: 'application/pdf' });
        const result = GitCodeApiService.validateImageFile(file);
        
        expect(result.valid).toBe(false);
        expect(result.error).toContain('不支持的图片格式');
      });

      it('应拒绝过大的文件', () => {
        // 创建一个超过 10MB 的模拟文件
        const largeContent = new Array(11 * 1024 * 1024).fill('a').join('');
        const file = new File([largeContent], 'large.png', { type: 'image/png' });
        const result = GitCodeApiService.validateImageFile(file);
        
        expect(result.valid).toBe(false);
        expect(result.error).toContain('图片文件过大');
      });
    });

    describe('getMimeType', () => {
      it('应返回文件的 MIME 类型', () => {
        const file = new File([''], 'test.png', { type: 'image/png' });
        expect(GitCodeApiService.getMimeType(file)).toBe('image/png');
      });

      it('应在类型为空时返回默认值', () => {
        const file = new File([''], 'test', { type: '' });
        expect(GitCodeApiService.getMimeType(file)).toBe('image/png');
      });
    });
  });
});

describe('GitCodeApiError', () => {
  it('应正确设置错误属性', () => {
    const error = new GitCodeApiError('测试错误', 'TEST_CODE', 500);
    
    expect(error.message).toBe('测试错误');
    expect(error.code).toBe('TEST_CODE');
    expect(error.statusCode).toBe(500);
    expect(error.name).toBe('GitCodeApiError');
  });

  it('应支持不带状态码的错误', () => {
    const error = new GitCodeApiError('测试错误', 'TEST_CODE');
    
    expect(error.statusCode).toBeUndefined();
  });
});
